// Colony.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

using Obtics.Values;

using Supremacy.Annotations;
using Supremacy.Buildings;
using Supremacy.Collections;
using Supremacy.Economy;
using Supremacy.Effects;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.IO.Serialization;
using Supremacy.Orbitals;
using Supremacy.Tech;
using Supremacy.Types;
using Supremacy.Utility;

namespace Supremacy.Universe
{
    /// <summary>
    /// Represents a colony in a star system.
    /// </summary>
    [Serializable]
    public class Colony : UniverseObject, IProductionCenter, ITradeCenter
    {
        #region TotalEnergy Dynamic Property
        public static readonly DynamicProperty<int> TotalEnergyProperty = DynamicProperty<int>.Register(
            "TotalEnergy", 
            typeof(Colony), 
            new DynamicPropertyMetadata<int>(OnTotalEnergyChanged));

        private static void OnTotalEnergyChanged(DynamicObject d, DynamicPropertyChangedEventArgs<int> e)
        {
            ((Colony)d).OnPropertyChanged("TotalEnergy");
        }

        public int TotalEnergy
        {
            get { return GetValue(TotalEnergyProperty).CurrentValue; }
        }
        #endregion

        /// <summary>
        /// The maximum number of production facilities per category that can exist
        /// on a single colony.
        /// </summary>
        public const int MaxProductionFacilities = 255;

        /// <summary>
        /// The base amount of food that is automatically produced without any food facilities
        /// being present in the system.  This must be enough to sustain a population that is
        /// smaller than the minimum labor allocation of a food production facility.
        /// </summary>
        public const int BaseFoodProduction = 10;

        private Obtics.Values.IValueProvider<int>[] _activeFacilities;
        private Obtics.Values.IValueProvider<int>[] _scrappedFacilities;
        private Obtics.Values.IValueProvider<int>[] _totalFacilities;
        private Obtics.Values.IValueProvider<int> _activeOrbitalBatteries;
        private Obtics.Values.IValueProvider<int> _scrappedOrbitalBatteries;
        private Obtics.Values.IValueProvider<int> _totalOrbitalBatteries;

        private ColonyFacilitiesAccessor _activeFacilitiesProvider;
        private ColonyFacilitiesAccessor _scrappedFacilitiesProvider;
        private ColonyFacilitiesAccessor _totalFacilitiesProvider;

        private byte _baseRawMaterials;

        private KeyedCollectionBase<GameObjectID, Building> _buildings;
        private ObservableList<BuildQueueItem> _buildQueue;
        private BuildSlot _buildSlot;
        private Meter _creditsFromTrade;
        private int[] _facilityTypes;
        private int _orbitalBatteryDesign;
        private Meter _foodReserves;
        private Percentage _health;
        private string _inhabitantId;
        private bool _isProductionAutomated;
        private Meter _morale;
        private short _originalOwnerId;
        private Meter _population;

        private int _shipyardId;
        private int _systemId = GameObjectID.InvalidID;
        private CollectionBase<TradeRoute> _tradeRoutes;

        private Colony()
        {
            Initialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Colony"/> class.
        /// </summary>
        /// <param name="system">The system in which the <see cref="Colony"/> will exist.</param>
        /// <param name="inhabitants">The race that will inhabit the <see cref="Colony"/>.</param>
        public Colony(StarSystem system, Race inhabitants)
            : this()
        {
            if (system == null)
                throw new ArgumentNullException("system");
            if (inhabitants == null)
                throw new ArgumentNullException("inhabitants");
            _health = 0;
            _population.Maximum = system.GetMaxPopulation(inhabitants);

            _inhabitantId = inhabitants.Key;

            _shipyardId = GameObjectID.InvalidID;
            _systemId = system.ObjectID;

            if (system.HasRawMaterialsBonus)
                _baseRawMaterials = (byte)(Statistics.Random(25) + 25);

            Location = system.Location;
            Owner = system.Owner;
            OriginalOwner = system.Owner;
        }

        public ColonyFacilitiesAccessor ActiveFacilities
        {
            get { return _activeFacilitiesProvider; }
        }

        public ColonyFacilitiesAccessor ScrappedFacilities
        {
            get { return _scrappedFacilitiesProvider; }
        }

        public ColonyFacilitiesAccessor TotalFacilities
        {
            get { return _totalFacilitiesProvider; }
        }

        public OrbitalBatteryDesign OrbitalBatteryDesign
        {
            get
            {
                if (_orbitalBatteryDesign == TechObjectDesign.InvalidDesignID)
                    return null;

                return GameContext.Current.TechDatabase[_orbitalBatteryDesign] as OrbitalBatteryDesign;
            }
            set
            {
                if (value == null)
                    _orbitalBatteryDesign = TechObjectDesign.InvalidDesignID;
                else
                    _orbitalBatteryDesign = value.DesignID;
            }
        }

        /// <summary>
        /// Gets the type of the <see cref="UniverseObject"/>.
        /// </summary>
        /// <value>The type of the <see cref="UniverseObject"/>.</value>
        public override sealed UniverseObjectType ObjectType
        {
            get { return UniverseObjectType.Colony; }
        }

        /// <summary>
        /// Gets the original owner of this <see cref="Colony"/>.
        /// </summary>
        /// <value>The original owner.</value>
        /// <remarks>
        /// The original owner of a <see cref="Colony"/> is the <see cref="Civilization"/>
        /// that originally established the colony.  This value never changes.
        /// </remarks>
        public Civilization OriginalOwner
        {
            get
            {
                if (_originalOwnerId == Civilization.InvalidID)
                    return null;
                return GameContext.Current.Civilizations[_originalOwnerId];
            }
            private set
            {
                _originalOwnerId = (value != null)
                                       ? (short)value.CivID
                                       : (short)Civilization.InvalidID;
            }
        }

        public Percentage GrowthRate
        {
            get
            {
                var baseValue = (decimal)(System.GetGrowthRate(Inhabitants) * 100);
                var modifier = new ValueModifier<decimal>
                               {
                                   IsOffsetAppliedFirst = false,
                                   HasCompoundMultiplier = false
                               };
                foreach (var building in this.Buildings)
                {
                    if (!building.IsActive)
                        continue;
                    foreach (var bonus in building.BuildingDesign.Bonuses)
                    {
                        if (bonus.BonusType == BonusType.GrowthRate)
                            modifier.Offset += bonus.Amount;
                        else if (bonus.BonusType == BonusType.PercentGrowthRate)
                            modifier.Multiplier += (0.01f * bonus.Amount);
                    }
                }
                return Convert.ToSingle(0.01m * modifier.Apply(baseValue));
            }
        }

        public int MaxPopulation
        {
            get
            {
                int totalMoonSizes = 0;
                int maxPopPerMoonSize = 0;
                int baseValue = this.System.GetMaxPopulation(Inhabitants);

                totalMoonSizes += this.System.Planets.SelectMany(p => p.Moons.Where(m => m.GetSize() == MoonSize.Small)).Sum(m => 1);
                totalMoonSizes += this.System.Planets.SelectMany(p => p.Moons.Where(m => m.GetSize() == MoonSize.Medium)).Sum(m => 2);
                totalMoonSizes += this.System.Planets.SelectMany(p => p.Moons.Where(m => m.GetSize() == MoonSize.Large)).Sum(m => 3);

                foreach (var building in this.Buildings)
                {
                    if (!building.IsActive)
                        continue;
                    foreach (var bonus in building.BuildingDesign.Bonuses)
                    {
                        if (bonus.BonusType == BonusType.MaxPopulationPerMoonSize)
                        {
                            maxPopPerMoonSize += bonus.Amount;
                        }
                    }
                }
                return (baseValue + (maxPopPerMoonSize * totalMoonSizes));
            }
        }

        /// <summary>
        /// Gets the food reserves meter for this  <see cref="Colony"/>.
        /// </summary>
        /// <value>The food reserves.</value>
        public Meter FoodReserves
        {
            get { return _foodReserves; }
        }

        /// <summary>
        /// Gets the star system in which this <see cref="Colony"/> resides.
        /// </summary>
        /// <value>The star system.</value>
        public StarSystem System
        {
            get { return GameContext.Current.Universe.Get<StarSystem>(_systemId); }
        }

        /// <summary>
        /// Gets or sets the name of this <see cref="Colony"/>.
        /// </summary>
        /// <value>The name.</value>
        public override string Name
        {
            get { return base.Name ?? ((System != null) ? System.Name : null); }
        }

        /// <summary>
        /// Gets the race that inhabitants this <see cref="Colony"/>.
        /// </summary>
        /// <value>The race.</value>
        public Race Inhabitants
        {
            get { return GameContext.Current.Races[_inhabitantId]; }
        }

        /// <summary>
        /// Gets or sets the shipyard present at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The shipyard.</value>
        public Shipyard Shipyard
        {
            get
            {
                if (_shipyardId == GameObjectID.InvalidID)
                    return null;
                return GameContext.Current.Universe.Objects[_shipyardId] as Shipyard;
            }
            set
            {
                if (value == null)
                    _shipyardId = GameObjectID.InvalidID;
                else
                    _shipyardId = value.ObjectID;

                OnPropertyChanged("Shipyard");
            }
        }

        /// <summary>
        /// Gets the population of this <see cref="Colony"/>.
        /// </summary>
        /// <value>The population.</value>
        public Meter Population
        {
            get { return _population; }
        }

        /// <summary>
        /// Gets the local morale at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The morale.</value>
        public Meter Morale
        {
            get { return _morale; }
        }

        /// <summary>
        /// Gets the buildings at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The buildings.</value>
        public IObservableIndexedCollection<Building> Buildings
        {
            get { return _buildings; }
        }

        /// <summary>
        /// Gets the active buildings at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The active buildings.</value>
        public IEnumerable<Building> ActiveBuildings
        {
            get
            {
                return Obtics.Collections.ObservableEnumerable.Where<Building>(
                    _buildings, 
                    o => Obtics.Values.ValueProvider.Property<Building, bool>(o, "IsActive"));
            }
        }

        /// <summary>
        /// Gets the buildings at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The buildings.</value>
        internal IList<Building> BuildingsInternal
        {
            get { return _buildings; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether production is automated.
        /// </summary>
        /// <value>
        /// <c>true</c> if production automated; otherwise, <c>false</c>.
        /// </value>
        public bool IsProductionAutomated
        {
            get { return _isProductionAutomated || !Owner.IsHuman; }
            set { _isProductionAutomated = value; }
        }

        /// <summary>
        /// Gets the scrap bonus at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The scrap bonus.</value>
        /// <remarks>
        /// The scrap bonus is a multiplier that gets applied to the amount of credits
        /// and resources recovered when scrapping an item at this <see cref="Colony"/>.
        /// </remarks>
        public Percentage ScrapBonus
        {
            get
            {
                Percentage result = 0.0f;
                foreach (var building in Buildings)
                {
                    if (!building.IsActive || building.Scrap)
                        continue;
                    foreach (var bonus in building.BuildingDesign.Bonuses)
                    {
                        if (bonus.BonusType == BonusType.PercentScrapping)
                        {
                            result += (0.01f * bonus.Amount);
                        }
                    }
                }
                return result;
            }
        }

        /// <summary>
        /// Gets the population health level at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The population health level.</value>
        public Percentage Health
        {
            get { return _health; }
        }

        #region Properties for System Panel Data Binding
        /// <summary>
        /// Gets the credits produced at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The credits.</value>
        public int TaxCredits
        {
            get
            {
                var modifier = new OutputModifier(0, 1.0f);
                float moraleMod = _morale.CurrentValue / (0.5f * MoraleHelper.MaxValue);
                modifier.Efficiency *= moraleMod;
                foreach (var building in Buildings)
                {
                    if (!building.IsActive)
                        continue;
                    foreach (var bonus in building.BuildingDesign.Bonuses)
                    {
                        if (bonus.BonusType == BonusType.Credits)
                            modifier.Bonus += bonus.Amount;
                        else if (bonus.BonusType == BonusType.PercentCredits)
                            modifier.Efficiency += (bonus.Amount / 100f);
                    }
                }
                return (int)((Population.CurrentValue * modifier.Efficiency) + modifier.Bonus);
            }
        }

        /// <summary>
        /// Gets the net food production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net food production.</value>
        public int NetFood
        {
            get
            {
                return (GetProductionOutput(ProductionCategory.Food)
                        - Population.CurrentValue);
            }
        }

        /// <summary>
        /// Gets the net industry production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net industry production.</value>
        public int NetIndustry
        {
            get { return GetProductionOutput(ProductionCategory.Industry); }
        }

        /// <summary>
        /// Gets the net energy production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net energy production.</value>
        public int NetEnergy
        {
            get
            {
                var energyUsed = this.Buildings
                    .Where(building => building.IsActive)
                    .Sum(building => building.BuildingDesign.EnergyCost);

                var shipyard = this.Shipyard;
                if (shipyard != null)
                {
                    energyUsed += shipyard.BuildSlots
                        .Where(o => o.IsActive)
                        .Sum(o => shipyard.ShipyardDesign.BuildSlotEnergyCost);
                }

                var orbitalBatteryDesign = this.OrbitalBatteryDesign;
                if (orbitalBatteryDesign != null)
                    energyUsed += (orbitalBatteryDesign.UnitEnergyCost * _activeOrbitalBatteries.Value);

                return (GetProductionOutput(ProductionCategory.Energy) - energyUsed);
            }
        }

        /// <summary>
        /// Gets the net research production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net research production.</value>
        public int NetResearch
        {
            get { return GetProductionOutput(ProductionCategory.Research); }
        }

        /// <summary>
        /// Gets the net intelligence production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net intelligence production.</value>
        public int NetIntelligence
        {
            get { return GetProductionOutput(ProductionCategory.Intelligence); }
        }

        /// <summary>
        /// Gets the net dilithium production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net dilithium production.</value>
        public int NetDilithium
        {
            get { return GetResourceProduction(ResourceType.Dilithium); }
        }

        /// <summary>
        /// Gets the net deuterium production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net deuterium production.</value>
        public int NetDeuterium
        {
            get { return GetResourceProduction(ResourceType.Deuterium); }
        }

/*
        /// <summary>
        /// Gets the net personnel production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net personnel production.</value>
        public int NetPersonnel
        {
            get { return GetResourceProduction(ResourceType.Personnel); }
        }
*/

        /// <summary>
        /// Gets the net raw materials production at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The net raw materials production.</value>
        public int NetRawMaterials
        {
            get { return GetResourceProduction(ResourceType.RawMaterials); }
        }
        #endregion

        #region IProductionCenter Members
        /// <summary>
        /// Gets or sets the owner ID of this <see cref="Colony"/>.  This should be the
        /// CivID property of the owner Civilization.
        /// </summary>
        /// <value>The owner ID.</value>
        public override GameObjectID OwnerID
        {
            get { return base.OwnerID; }
            set
            {
                base.OwnerID = value;
                if ((System != null) && (value != GameObjectID.InvalidID))
                    System.OwnerID = value;
                if (_originalOwnerId == GameObjectID.InvalidID)
                    _originalOwnerId = (short)value;
            }
        }

        /// <summary>
        /// Gets the build queue at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The build queue.</value>
        public IList<BuildQueueItem> BuildQueue
        {
            get { return _buildQueue; }
        }

        /// <summary>
        /// Gets the build slots at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The build slots.</value>
        [NotNull]
        public BuildSlot[] BuildSlots
        {
            get { return new[] { _buildSlot }; }
        }

        /// <summary>
        /// Gets the build output for the specified build slot number.
        /// </summary>
        /// <param name="slot">The build slot number.</param>
        /// <returns>The build output.</returns>
        public int GetBuildOutput(int slot)
        {
            return GetProductionOutput(ProductionCategory.Industry);
        }

        /// <summary>
        /// Remove any completed projects from the build slots and dequeue new projects
        /// as slots become available.
        /// </summary>
        public void ProcessQueue()
        {
            foreach (var slot in this.BuildSlots)
            {
                if ((slot.Project != null) && slot.Project.IsCancelled)
                    slot.Project = null;

                if (slot.Project != null)
                    continue;

                var queueItem = this.BuildQueue.FirstOrDefault();
                if (queueItem == null)
                    continue;

                if (queueItem.Count > 1)
                {
                    slot.Project = queueItem.Project.CloneEquivalent();
                    queueItem.DecrementCount();
                }
                else
                {
                    slot.Project = queueItem.Project;
                    this.BuildQueue.Remove(queueItem);
                }
            }
        }
        #endregion

        #region ITradeCenter Members
        /// <summary>
        /// Gets the trade routes available at this <see cref="Colony"/>.
        /// </summary>
        /// <value>The trade routes.</value>
        public IList<TradeRoute> TradeRoutes
        {
            get { return _tradeRoutes; }
        }

        public Meter CreditsFromTrade
        {
            get { return _creditsFromTrade; }
        }

        public void UpdateCreditsFromTrade()
        {
            this.ResetCreditsFromTrade();
        }
        #endregion

        private void Initialize()
        {
            var categories = Enum.GetValues(typeof(ProductionCategory));

            EnsureValueProviders();

            _facilityTypes = new int[categories.Length];

            for (var i = 0; i < _facilityTypes.Length; i++)
                _facilityTypes[i] = TechObjectDesign.InvalidDesignID;

            _orbitalBatteryDesign = TechObjectDesign.InvalidDesignID;

            _population = new Meter(0, 0, Meter.MaxValue);
            _population.PropertyChanged += PopulationPropertyChanged;

            _morale = new Meter(110, MoraleHelper.MinValue, MoraleHelper.MaxValue);

            _foodReserves = new Meter(0, Meter.MaxValue);

            _tradeRoutes = new CollectionBase<TradeRoute>();
            _buildings = new KeyedCollectionBase<GameObjectID, Building>(o => o.ObjectID);
            _creditsFromTrade = new Meter();
            _buildSlot = new BuildSlot();
            _buildQueue = new ObservableList<BuildQueueItem>();

            _baseRawMaterials = 0;
        }

        /// <summary>
        /// Handles the PropertyChanged event of the Population control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="PropertyChangedEventArgs"/> instance containing the event data.</param>
        protected void PopulationPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CurrentValue")
                OnPropertyChanged("NetFood");
        }

        /// <summary>
        /// Captures this <see cref="Colony"/>.
        /// </summary>
        /// <param name="newOwner">The new owner.</param>
        /// <remarks>
        /// When capturing, the Owner of the <see cref="Colony"/> is changed, and the
        /// capture result of each building is checked and executed. 
        /// </remarks>
        public void Capture(Civilization newOwner)
        {
            if (newOwner == null)
                return;
            Owner = newOwner;
            var destroyedBuildings = new List<Building>();
            foreach (var building in Buildings)
            {
                if (building.BuildingDesign.CaptureResult == CaptureResult.Destroy)
                {
                    destroyedBuildings.Add(building);
                }
            }
            foreach (var destroyedBuilding in destroyedBuildings)
            {
                GameContext.Current.Universe.Destroy(destroyedBuilding);
            }
        }

        /// <summary>
        /// Called when the Build Queue changes.
        /// </summary>
        internal void OnBuildQueueChanged()
        {
            OnPropertyChanged("BuildQueue");
        }

        /// <summary>
        /// Adds a building to this <see cref="Colony"/>.
        /// </summary>
        /// <param name="building">The building to add.</param>
        public void AddBuilding(Building building)
        {
            if (Buildings.Contains(building))
                return;
            building.IsActive = false;
            building.Location = Location;
            _buildings.Add(building);
            ActivateBuilding(building);
        }

        /// <summary>
        /// Removes a building from this <see cref="Colony"/>.
        /// </summary>
        /// <param name="building">The building to remove.</param>
        public void RemoveBuilding(Building building)
        {
            if (!Buildings.Contains(building))
                return;
            DeactivateBuilding(building);
            _buildings.Remove(building);
            if (Shipyard == building)
                Shipyard = null;
        }

        /// <summary>
        /// Adds a single facility of the specified category to this <see cref="Colony"/>.
        /// </summary>
        /// <param name="category">The production category.</param>
        public void AddFacility(ProductionCategory category)
        {
            AddFacilities(category, 1);
        }

        /// <summary>
        /// Adds multiple facilities of the specified cagory to this <see cref="Colony"/>.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <param name="count">The number of facilities to add.</param>
        public void AddFacilities(ProductionCategory category, int count)
        {
            _totalFacilities[(int)category].Value += (byte)count;
        }

        /// <summary>
        /// Removes a single facility of the specified category from this <see cref="Colony"/>.
        /// </summary>
        /// <param name="category">The production category.</param>
        public void RemoveFacility(ProductionCategory category)
        {
            RemoveFacilies(category, 1);
        }

        /// <summary>
        /// Removes multiple facilities of the specified cagory from this <see cref="Colony"/>.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <param name="count">The number of facilities to remove.</param>
        public void RemoveFacilies(ProductionCategory category, int count)
        {
            int toDeactivate = -(_totalFacilities[(int)category].Value - _activeFacilities[(int)category].Value - count);
            for (int i = 0; i < toDeactivate; i++)
                DeactivateFacility(category);
            _totalFacilities[(int)category].Value -= (byte)count;
        }

        /// <summary>
        /// Gets the number of facilities to be scrapped for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The number of facilities to be scrapped.</returns>
        public int GetScrappedFacilities(ProductionCategory category)
        {
            return _scrappedFacilities[(int)category].Value;
        }

        /// <summary>
        /// Sets the number of facilities to be scrapped for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <param name="count">The number of facilities to be scrapped.</param>
        public void SetScrappedFacilities(ProductionCategory category, int count)
        {
            if (count < 0)
                count = 0;
            else if (count > _totalFacilities[(int)category].Value)
                count = _totalFacilities[(int)category].Value;
            _scrappedFacilities[(int)category].Value = (byte)count;
        }

        /// <summary>
        /// Perform the actual scrapping of the facilities designated as scrap.
        /// </summary>
        public void ScrapNonStructures()
        {
            foreach (var category in EnumHelper.GetValues<ProductionCategory>())
            {
                while (_scrappedFacilities[(int)category].Value > 0)
                {
                    RemoveFacility(category);
                    _scrappedFacilities[(int)category].Value--;
                }
            }

            while (_scrappedOrbitalBatteries.Value > 0)
            {
                _scrappedOrbitalBatteries.Value--;
                this.RemoveOrbitalBatteries(1);
                this.OnPropertyChanged("ScrappedOrbitalBatteries");
            }
        }

        /// <summary>
        /// Gets the natural production level of a given resource for this colony.
        /// </summary>
        /// <param name="resourceType">The resource type.</param>
        /// <param name="currentPopulation">The current population of the colony.</param>
        /// <returns>The natural production level.</returns>
        protected internal int GetBaseResourceProduction(ResourceType resourceType, int currentPopulation)
        {
            switch (resourceType)
            {
                case ResourceType.Deuterium:
                    return this.System.Planets.Where(p => p.PlanetType.IsGaseous()).Sum(p => 10);
                case ResourceType.RawMaterials:
                    return _baseRawMaterials;
/*
                case ResourceType.Personnel:
                    int baseValue = 0;
                    var personnelTable = GameContext.Current.Tables.ResourceTables["BasePersonnel"];
                    if (personnelTable != null)
                    {
                        if (personnelTable[Owner.Key] != null)
                            baseValue = Number.ParseInt32(personnelTable[Owner.Key][0]);
                        else
                            baseValue = Number.ParseInt32(personnelTable[0][0]);
                        baseValue = (int)(0.01f * baseValue * currentPopulation);
                    }
                    return baseValue;
*/
                default:
                    return 0;
            }
        }

        /// <summary>
        /// Gets the natural production level of a given resource for this colony.
        /// </summary>
        /// <param name="resourceType">The resource type.</param>
        /// <returns>The natural production level.</returns>
        protected internal int GetBaseResourceProduction(ResourceType resourceType)
        {
            return GetBaseResourceProduction(resourceType, Population.CurrentValue);
        }

        /// <summary>
        /// Gets the amount of output for the specified production category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The production output.</returns>
        public int GetProductionOutput(ProductionCategory category)
        {
            int unitOutput = GetFacilityType(category).UnitOutput;
            int activeUnits = GetActiveFacilities(category);
            var modifier = GetProductionModifier(category);
            int baseOutput = unitOutput * activeUnits;
            switch (category)
            {
                case ProductionCategory.Food:
                    modifier.Bonus += BaseFoodProduction;
                    break;
                case ProductionCategory.Research:
                case ProductionCategory.Industry:
                    float moraleMod = _morale.CurrentValue / (0.5f * MoraleHelper.MaxValue);
                    baseOutput = (int)(moraleMod * baseOutput);
                    break;
            }
            return ((int)(baseOutput + (baseOutput * modifier.Efficiency)) + modifier.Bonus);
        }

        /// <summary>
        /// Gets the estimated resource production levels for the next turn.
        /// </summary>
        /// <returns>The estimated resource production levels for the next turn.</returns>
        public ResourceValueCollection GetEstimatedResourcesProduction()
        {
            var values = new ResourceValueCollection();
            foreach (var resourceType in EnumHelper.GetValues<ResourceType>())
                values[resourceType] = GetResourceProduction(resourceType);
            return values;
        }

        /// <summary>
        /// Gets the current production output for the specified resource design.
        /// </summary>
        /// <param name="resource">The resource design.</param>
        /// <returns>The production output.</returns>
        public int GetResourceProduction(ResourceType resource)
        {
            int baseValue = 0;
            var modifier = GetResourceModifier(resource);
            /*if (resource == ResourceType.Personnel)
            {
                baseValue = GetBaseResourceProduction(resource);
            }
            else*/ if (resource == ResourceType.RawMaterials)
            {
                baseValue = GetBaseResourceProduction(resource);
            }
            else if (resource == ResourceType.Deuterium)
            {
                baseValue = GetBaseResourceProduction(ResourceType.Deuterium);
            }
            return (int)(baseValue + (baseValue * modifier.Efficiency) + modifier.Bonus);
        }

        /// <summary>
        /// Gets the resource production modifier for the specified resource design.
        /// </summary>
        /// <param name="resource">The resource design.</param>
        /// <returns>The resource production modifier.</returns>
        public OutputModifier GetResourceModifier(ResourceType resource)
        {
            var modifier = new OutputModifier(0, 0.0f);

            foreach (var building in Buildings)
            {
                if (!building.IsActive)
                    continue;
                foreach (var bonus in building.BuildingDesign.Bonuses)
                {
                    switch (resource)
                    {
                        case ResourceType.Deuterium:
                            if (bonus.BonusType == BonusType.Deuterium)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentDeuterium)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
                        case ResourceType.Dilithium:
                            if (bonus.BonusType == BonusType.Dilithium)
                                modifier.Bonus += bonus.Amount;
                            break;
/*
                        case ResourceType.Personnel:
                            if (bonus.BonusType == BonusType.Personnel)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentPersonnelTraining)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
*/
                        case ResourceType.RawMaterials:
                            if (bonus.BonusType == BonusType.RawMaterials)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentRawMaterials)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
                    }
                }
            }

            return modifier;
        }

        /// <summary>
        /// Gets the production output modifier for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The production output modifier.</returns>
        public OutputModifier GetProductionModifier(ProductionCategory category)
        {
            var modifier = new OutputModifier(0, 0.0f);
            Percentage moraleMod = _morale.CurrentValue / (0.5f * MoraleHelper.MaxValue);

            if (category == ProductionCategory.Food)
            {
                foreach (var planet in System.Planets)
                {
                    if (planet.HasFoodBonus)
                        modifier.Efficiency += 0.15f;
                }
            }
            else if (category == ProductionCategory.Energy)
            {
                foreach (var planet in System.Planets)
                {
                    if (planet.HasEnergyBonus)
                        modifier.Efficiency += 0.15f;
                }
            }

            foreach (var building in Buildings)
            {
                if (!building.IsActive)
                    continue;
                foreach (var bonus in building.BuildingDesign.Bonuses)
                {
                    switch (category)
                    {
                        case ProductionCategory.Food:
                            if (bonus.BonusType == BonusType.Food)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentFood)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
                        case ProductionCategory.Industry:
                            if (bonus.BonusType == BonusType.Industry)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentIndustry)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
                        case ProductionCategory.Energy:
                            if (bonus.BonusType == BonusType.Energy)
                                modifier.Bonus += bonus.Amount;
                            else if (bonus.BonusType == BonusType.PercentEnergy)
                                modifier.Efficiency += (bonus.Amount / 100f);
                            break;
                        case ProductionCategory.Research:
                            if (bonus.BonusType == BonusType.Research)
                                modifier.Bonus += bonus.Amount;
                            break;
                        case ProductionCategory.Intelligence:
                            if (bonus.BonusType == BonusType.Intelligence)
                                modifier.Bonus += bonus.Amount;
                            break;
                    }
                }
            }

            if (category == ProductionCategory.Industry)
                modifier.Bonus += Population.CurrentValue;

            modifier.Efficiency *= moraleMod;

            return modifier;
        }

        /// <summary>
        /// Gets the number of active facilities for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The number of active facilities.</returns>
        public int GetActiveFacilities(ProductionCategory category)
        {
            return _activeFacilities[(int)category].Value;
        }

        /// <summary>
        /// Gets the total number of facilities for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The total number of facilities.</returns>
        public int GetTotalFacilities(ProductionCategory category)
        {
            return _totalFacilities[(int)category].Value;
        }

        /// <summary>
        /// Gets the production facility design for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns>The production facility design.</returns>
        public ProductionFacilityDesign GetFacilityType(ProductionCategory category)
        {
            var facilityId = _facilityTypes[(int)category];
            if (facilityId == TechObjectDesign.InvalidDesignID)
                return null;
            return GameContext.Current.TechDatabase.ProductionFacilityDesigns[facilityId];
        }

        /// <summary>
        /// Sets the production facility design for the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <param name="design">The design.</param>
        public void SetFacilityType(ProductionCategory category, ProductionFacilityDesign design)
        {
            if (design == null)
            {
                _facilityTypes[(int)category] = TechObjectDesign.InvalidDesignID;
                return;
            }
            
            if (design.Category != category)
                throw new ArgumentException("Production category mismatch");

            _facilityTypes[(int)category] = design.DesignID;
        }

        /// <summary>
        /// Activates a production facility in the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public bool ActivateFacility(ProductionCategory category)
        {
            lock (_activeFacilities)
            {
                if (_activeFacilities[(int)category].Value >= _totalFacilities[(int)category].Value)
                    return false;
                var facilityType = GetFacilityType(category);
                if (facilityType == null)
                    return false;
                if (facilityType.LaborCost > GetAvailableLabor())
                    return false;
                _activeFacilities[(int)category].Value++;
            }
            switch (category)
            {
                case ProductionCategory.Food:
                    OnPropertyChanged("NetFood");
                    break;
                case ProductionCategory.Industry:
                    OnPropertyChanged("NetIndustry");
                    break;
                case ProductionCategory.Energy:
                    OnPropertyChanged("NetEnergy");
                    break;
                case ProductionCategory.Research:
                    OnPropertyChanged("NetResearch");
                    break;
                case ProductionCategory.Intelligence:
                    OnPropertyChanged("NetIntelligence");
                    break;
            }
            this.InvalidateBuildTimes();
            if (this.Shipyard != null)
                this.Shipyard.InvalidateBuildTimes();
            return true;
        }

        /// <summary>
        /// Deactivates a production facility in the specified category.
        /// </summary>
        /// <param name="category">The production category.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public bool DeactivateFacility(ProductionCategory category)
        {
            lock (_activeFacilities)
            {
                if (_activeFacilities[(int)category].Value < 1)
                    return false;
                _activeFacilities[(int)category].Value--;
            }
            switch (category)
            {
                case ProductionCategory.Food:
                    OnPropertyChanged("NetFood");
                    break;
                case ProductionCategory.Industry:
                    OnPropertyChanged("NetIndustry");
                    break;
                case ProductionCategory.Energy:
                    OnPropertyChanged("NetEnergy");
                    break;
                case ProductionCategory.Research:
                    OnPropertyChanged("NetResearch");
                    break;
                case ProductionCategory.Intelligence:
                    OnPropertyChanged("NetIntelligence");
                    break;
            }
            this.InvalidateBuildTimes();
            if (this.Shipyard != null)
                this.Shipyard.InvalidateBuildTimes();
            return true;
        }

        /// <summary>
        /// Gets the amount of the population that is available for labor.
        /// </summary>
        /// <returns>The amount of the population that is available for labor.</returns>
        public int GetAvailableLabor()
        {
            int laborUsed = 0;
            for (int i = 0; i < _activeFacilities.Length; i++)
            {
                var facilityType = GetFacilityType((ProductionCategory)i);
                if (facilityType != null)
                    laborUsed += (_activeFacilities[i].Value * facilityType.LaborCost);
            }
            return (Population.CurrentValue - laborUsed);
        }

        /// <summary>
        /// Determines whether a <see cref="Building"/> of the specified design exists at this <see cref="Colony"/>.
        /// </summary>
        /// <param name="design">The building design.</param>
        /// <returns><c>true</c> if a building of the specified design exists; otherwise, <c>false</c>.</returns>
        internal bool HasBuilding(BuildingDesign design)
        {
            return HasBuilding(design, false);
        }

        /// <summary>
        /// Determines whether a <see cref="Shipyard"/> of the specified design exists at this <see cref="Colony"/>.
        /// </summary>
        /// <param name="design">The shipyard design.</param>
        /// <returns><c>true</c> if a shipyard of the specified design exists; otherwise, <c>false</c>.</returns>
        internal bool HasShipyard(ShipyardDesign design)
        {
            var shipyard = this.Shipyard;

            return shipyard != null &&
                   shipyard.ShipyardDesign == design;
        }

        /// <summary>
        /// Determines whether a <see cref="Building"/> of the specified design exists at this <see cref="Colony"/>.
        /// </summary>
        /// <param name="design">The building design.</param>
        /// <param name="isActive">Whether or not the building must be powered.</param>
        /// <returns><c>true</c> if a building of the specified design exists; otherwise, <c>false</c>.</returns>
        internal bool HasBuilding(BuildingDesign design, bool isActive)
        {
            return Buildings.Any(building => (building.Design == design) && (!isActive || building.IsActive));
        }

        /// <summary>
        /// Gets the current energy consumption at this <see cref="Colony"/>.
        /// </summary>
        /// <returns>The current energy consumption.</returns>
        internal int GetEnergyUsage()
        {
            return Buildings.Where(building => building.IsActive).Sum(building => ((BuildingDesign)building.Design).EnergyCost);
        }

        /// <summary>
        /// Ensures that enough energy is available for all active buildings.  If insufficient
        /// energy is available, then buildings will be shut down systematically until the
        /// situation is resolved.
        /// </summary>
        /// <returns>The number of buildings that were shut down.</returns>
        public int EnsureEnergyForBuildings()
        {
            var shutDown = 0;
            var shipyard = this.Shipyard;

            while (true)
            {
                var netEnergy = this.NetEnergy;
                if (netEnergy >= 0)
                    break;

                /*
                 * First try to shut down any unutilized shipyard build slots.  Those can be considered
                 * less critical than active buildings.
                 */
                if (shipyard != null)
                {
                    var deactivatedBuildSlot = shipyard.BuildSlots
                        .Where(o => o.IsActive && !o.HasProject)
                        .Where(DeactivateShipyardBuildSlot)
                        .FirstOrDefault();

                    if (deactivatedBuildSlot != null)
                    {
                        ++shutDown;
                        goto Next;
                    }
                }

                /*
                 * Next, try to shut down some buildings.  First check to see if we can get away with shutting
                 * down just one building.  If not, start with the most expensive active building in hopes of
                 * minimizing the number we have to shut down.
                 */
                var mostCostlyBuilding = this.Buildings
                    .Where(o => o.IsActive && !o.BuildingDesign.AlwaysOnline)
                    .OrderBy(o => o.BuildingDesign.EnergyCost)
                    .FirstOrDefault(o => o.BuildingDesign.EnergyCost >= -netEnergy);

                if (mostCostlyBuilding == null)
                {
                    mostCostlyBuilding = this.Buildings
                        .Where(o => o.IsActive && !o.BuildingDesign.AlwaysOnline)
                        .OrderByDescending(o => o.BuildingDesign.EnergyCost)
                        .FirstOrDefault();
                }

                if (mostCostlyBuilding != null &&
                    DeactivateBuilding(mostCostlyBuilding))
                {
                    shutDown++;
                    goto Next;
                }

                foreach (var building in Buildings.Where(o => o.IsActive && !o.BuildingDesign.AlwaysOnline).OrderByDescending(o => o.BuildingDesign.EnergyCost))
                {
                    if (DeactivateBuilding(building))
                    {
                        shutDown++;
                        goto Next;
                    }
                }

                /*
                 * Lastly, try to shut down some shipyard build slots.  To be fair to the player, we'll favor
                 * shutting down build slots with the least build progress.
                 */
                if (shipyard != null)
                {
                    var deactivatedBuildSlot = shipyard.BuildSlots
                        .Where(o => o.IsActive && !o.HasProject)
                        .Where(DeactivateShipyardBuildSlot)
                        .FirstOrDefault();

                    if (deactivatedBuildSlot != null)
                    {
                        ++shutDown;
                        goto Next;
                    }
                }

                break;
            Next:
                continue;
            }
            
            return shutDown;
        }

        public int ActiveOrbitalBatteries
        {
            get { return _activeOrbitalBatteries.Value; }
        }

        public int TotalOrbitalBatteries
        {
            get { return _totalOrbitalBatteries.Value; }
        }

        public int ScrappedOrbitalBatteries
        {
            get { return _scrappedOrbitalBatteries.Value; }
        }

        public bool ActivateOrbitalBattery()
        {
            var design = this.OrbitalBatteryDesign;
            if (design == null)
                return false;

            lock (_activeOrbitalBatteries)
            {
                if (_activeOrbitalBatteries.Value >= _totalOrbitalBatteries.Value)
                    return false;

                if (design.UnitEnergyCost > 0)
                {
                    var netEnergy = this.NetEnergy;
                    if (netEnergy - design.UnitEnergyCost < 0)
                        return false;
                }

                ++_activeOrbitalBatteries.Value;
            }

            if (design.UnitEnergyCost > 0)
                this.OnPropertyChanged("NetEnergy");

            this.OnPropertyChanged("ActiveOrbitalBatteries");

            return true;
        }

        public bool DeactivateOrbitalBattery()
        {
            var design = this.OrbitalBatteryDesign;
            if (design == null)
                return false;

            lock (_activeOrbitalBatteries)
            {
                if (_activeOrbitalBatteries.Value <= 0)
                    return false;

                --_activeOrbitalBatteries.Value;
            }

            if (design.UnitEnergyCost > 0)
                this.OnPropertyChanged("NetEnergy");

            this.OnPropertyChanged("ActiveOrbitalBatteries");

            return true;
        }

        public void SetScrappedOrbitalBatteries(int count)
        {
            if (count < 0)
                count = 0;
            else if (count > _totalOrbitalBatteries.Value)
                count = _totalOrbitalBatteries.Value;
            _scrappedOrbitalBatteries.Value = (byte)count;
        }

        public void RemoveOrbitalBatteries(int count)
        {
            var toDeactivate = -(_totalOrbitalBatteries.Value - _activeOrbitalBatteries.Value - count);
            
            for (var i = 0; i < toDeactivate; i++)
            {
                this.DeactivateOrbitalBattery();
            }

            _totalOrbitalBatteries.Value = (byte)(_totalOrbitalBatteries.Value - count);

            this.OnPropertyChanged("TotalOrbitalBatteries");
        }

        public void AddOrbitalBatteries(int count)
        {
            _totalOrbitalBatteries.Value = (byte)(_totalOrbitalBatteries.Value + count);

            this.OnPropertyChanged("TotalOrbitalBatteries");
        }

        /// <summary>
        /// Deactivates the specified building.
        /// </summary>
        /// <param name="building">The building to deactivate.</param>
        public bool DeactivateBuilding(Building building)
        {
            return SetBuildingActive(building, false);
        }

        /// <summary>
        /// Activates the specified building.
        /// </summary>
        /// <param name="building">The building to activate.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public bool ActivateBuilding(Building building)
        {
            return SetBuildingActive(building, true);
        }

        public bool ActivateShipyardBuildSlot(ShipyardBuildSlot buildSlot)
        {
            if (buildSlot == null)
                return false;

            var shipyard = this.Shipyard;
            if (shipyard == null || !Equals(shipyard, buildSlot.Shipyard))
                return false;

            if (buildSlot.IsActive)
                return true;

            if (shipyard.ShipyardDesign.BuildSlotEnergyCost > this.NetEnergy)
                return false;

            buildSlot.IsActive = true;
            
            this.OnPropertyChanged("NetEnergy");

            return true;
        }

        public bool DeactivateShipyardBuildSlot(ShipyardBuildSlot buildSlot)
        {
            if (buildSlot == null)
                return false;

            var shipyard = this.Shipyard;
            if (shipyard == null || !Equals(shipyard, buildSlot.Shipyard))
                return false;

            if (!buildSlot.IsActive)
                return true;

            buildSlot.IsActive = false;

            this.OnPropertyChanged("NetEnergy");

            return true;
        }

        private bool SetBuildingActive(Building building, bool value)
        {
            if (building == null)
                return false;
            if (building.IsActive == value)
                return true;
            if (value && building.BuildingDesign.AlwaysOnline)
                return false;
            if (!Buildings.Contains(building))
                return false;

            bool result = true;
            var propertyChanges = new HashSet<string>();

            if (value)
            {
                result = (building.BuildingDesign.EnergyCost <= NetEnergy);
            }

            if (result)
            {
                building.IsActive = value;
                propertyChanges.Add("NetEnergy");
                foreach (var bonus in building.BuildingDesign.Bonuses)
                {
                    if (BonusHelper.IsGlobalBonus(bonus.BonusType))
                    {
                        var civManager = GameContext.Current.CivilizationManagers[OwnerID];
                        if (value)
                        {
                            civManager.GlobalBonuses.Add(bonus);
                        }
                        else
                        {
                            civManager.GlobalBonuses.Remove(bonus);
                        }
                        civManager.Research.RefreshBonuses();
                    }
                    else
                    {
                        switch (bonus.BonusType)
                        {
                            case BonusType.Credits:
                            case BonusType.PercentCredits:
                            case BonusType.PercentTotalCredits:
                            case BonusType.PercentTradeIncome:
                                propertyChanges.Add("Credits");
                                break;
                            case BonusType.Food:
                            case BonusType.PercentFood:
                                propertyChanges.Add("NetFood");
                                break;
                            case BonusType.Industry:
                            case BonusType.PercentIndustry:
                                propertyChanges.Add("NetIndustry");
                                this.InvalidateBuildTimes();
                                if (this.Shipyard != null)
                                    this.Shipyard.InvalidateBuildTimes();
                                break;
                            case BonusType.Energy:
                            case BonusType.PercentEnergy:
                                propertyChanges.Add("NetEnergy");
                                break;
                            case BonusType.Research:
                                propertyChanges.Add("NetResearch");
                                break;
                            case BonusType.Intelligence:
                                propertyChanges.Add("NetIntelligence");
                                break;
                            //case BonusType.Personnel:
                            //case BonusType.PercentPersonnelTraining:
                            //    propertyChanges.Add("NetPersonnel");
                            //    break;
                            case BonusType.Dilithium:
                                propertyChanges.Add("NetDilithium");
                                break;
                            case BonusType.Deuterium:
                            case BonusType.PercentDeuterium:
                                propertyChanges.Add("NetDeuterium");
                                break;
                            case BonusType.RawMaterials:
                            case BonusType.PercentRawMaterials:
                                propertyChanges.Add("NetRawMaterials");
                                break;
                            case BonusType.GrowthRate:
                            case BonusType.PercentGrowthRate:
                                propertyChanges.Add("GrowthRate");
                                break;
                            default:
                                break;
                        }
                    }
                }
                foreach (var propertyChange in propertyChanges)
                {
                    OnPropertyChanged(propertyChange);
                }
            }

            return result;
        }

        /// <summary>
        /// Determines whether the a building of the specified design is under construction.
        /// </summary>
        /// <param name="design">The building design.</param>
        /// <returns>
        /// <c>true</c> if a building of the specified design is under construction; otherwise, <c>false</c>.
        /// </returns>
        internal bool IsBuilding(TechObjectDesign design)
        {
            for (int i = 0; i < BuildSlots.Length; i++)
            {
                if ((BuildSlots[i].Project != null)
                    && (BuildSlots[i].Project.BuildDesign == design))
                {
                    return true;
                }
            }
            foreach (var item in BuildQueue)
            {
                if (item.Project.BuildDesign == design)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Compacts this <see cref="Colony"/> to reduce the serialization footprint.
        /// </summary>
        public override void Compact()
        {
            base.Compact();
            _tradeRoutes.TrimExcess();
            //_buildings.TrimExcess();
        }

        public override void SerializeOwnedData(SerializationWriter writer, object context)
        {
            base.SerializeOwnedData(writer, context);
            writer.WriteOptimized(_systemId);
            writer.WriteOptimized(_originalOwnerId);
            writer.WriteOptimized(_inhabitantId);
            writer.Write(_shipyardId);
            writer.Write(_health);
            _population.SerializeOwnedData(writer, context);
            _morale.SerializeOwnedData(writer, context);
            _foodReserves.SerializeOwnedData(writer, context);
            writer.Write(_baseRawMaterials);
            writer.Write(_isProductionAutomated);
            writer.WriteOptimized(this.TradeRoutes.ToArray());
            writer.WriteBytesDirect(_scrappedFacilities.Select(o => (byte)o.Value).ToArray());
            writer.WriteBytesDirect(_activeFacilities.Select(o => (byte)o.Value).ToArray());
            writer.WriteBytesDirect(_totalFacilities.Select(o => (byte)o.Value).ToArray());
            writer.WriteOptimized(_facilityTypes);
            _creditsFromTrade.SerializeOwnedData(writer, context);
            _buildSlot.SerializeOwnedData(writer, context);
            writer.WriteOptimized(_buildQueue.ToArray());

            writer.Write(_orbitalBatteryDesign);
            writer.Write((byte)_activeOrbitalBatteries.Value);
            writer.Write((byte)_totalOrbitalBatteries.Value);
            writer.Write((byte)_scrappedOrbitalBatteries.Value);
        }

        [Serializable]
        private sealed class ObservableValueProvider<T> : IValueProvider<T>, INotifyPropertyChanged
        {
            object IValueProvider.Value
            {
                get { return this.Value; }
                set { this.Value = (T)value; }
            }

            #region Value Property
            private T _value;

            public T Value
            {
                get { return _value; }
                set
                {
                    _value = value;
                    this.OnPropertyChanged("Value");
                }
            }
            #endregion

            public bool IsReadOnly
            {
                get { return false; }
            }

            #region Implementation of INotifyPropertyChanged
            [field: NonSerialized]
            public event PropertyChangedEventHandler PropertyChanged;

            private void OnPropertyChanged(string propertyName)
            {
                var handler = this.PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs(propertyName));
            }
            #endregion
        }

        private void EnsureValueProviders()
        {
            var categories = Enum.GetValues(typeof(ProductionCategory));

            _activeFacilities = Enumerable.Range(1, categories.Length).Select(o => new ObservableValueProvider<int>()).ToArray();
            _totalFacilities = Enumerable.Range(1, categories.Length).Select(o => new ObservableValueProvider<int>()).ToArray();
            _scrappedFacilities = Enumerable.Range(1, categories.Length).Select(o => new ObservableValueProvider<int>()).ToArray();

            _activeOrbitalBatteries = new ObservableValueProvider<int>();
            _totalOrbitalBatteries = new ObservableValueProvider<int>();
            _scrappedOrbitalBatteries = new ObservableValueProvider<int>();

            _activeFacilitiesProvider = new ColonyFacilitiesAccessor(_activeFacilities);
            _scrappedFacilitiesProvider = new ColonyFacilitiesAccessor(_scrappedFacilities);
            _totalFacilitiesProvider = new ColonyFacilitiesAccessor(_totalFacilities);
        }

        public override void DeserializeOwnedData(SerializationReader reader, object context)
        {
            base.DeserializeOwnedData(reader, context);

            Initialize();

            _systemId = reader.ReadOptimizedInt32();
            _originalOwnerId = reader.ReadOptimizedInt16();
            _inhabitantId = reader.ReadOptimizedString();
            _shipyardId = reader.ReadInt32();
            _health = reader.ReadSingle();
            _population.DeserializeOwnedData(reader, context);
            _morale.DeserializeOwnedData(reader, context);
            _foodReserves.DeserializeOwnedData(reader, context);
            _baseRawMaterials = reader.ReadByte();
            _isProductionAutomated = reader.ReadBoolean();
            _tradeRoutes.AddRange((TradeRoute[])reader.ReadOptimizedObjectArray(typeof(TradeRoute)));
            reader.ReadBytesDirect(_scrappedFacilities.Length).ForEach((o, i) => _scrappedFacilities[i].Value = o);
            reader.ReadBytesDirect(_activeFacilities.Length).ForEach((o, i) => _activeFacilities[i].Value = o);
            reader.ReadBytesDirect(_totalFacilities.Length).ForEach((o, i) => _totalFacilities[i].Value = o);
            reader.ReadOptimizedInt32Array().CopyTo(_facilityTypes, 0);
            _creditsFromTrade.DeserializeOwnedData(reader, context);
            _buildSlot.DeserializeOwnedData(reader, context);
            _buildQueue.AddRange((BuildQueueItem[])reader.ReadOptimizedObjectArray(typeof(BuildQueueItem)));
            _orbitalBatteryDesign = reader.ReadInt32();
            _activeOrbitalBatteries.Value = reader.ReadByte();
            _totalOrbitalBatteries.Value = reader.ReadByte();
            _scrappedOrbitalBatteries.Value = reader.ReadByte();
        }

        public override void CloneFrom(Cloneable source)
        {
            var typedSource = (Colony)source;

            base.CloneFrom(source);

            this.EnsureValueProviders();

            _systemId = typedSource._systemId;
            _shipyardId = typedSource._shipyardId;
            _originalOwnerId = typedSource._originalOwnerId;
            _inhabitantId = typedSource._inhabitantId;

            _health = typedSource._health;
            _isProductionAutomated = typedSource._isProductionAutomated;

            typedSource._activeFacilities.ForEach((v, i) => _activeFacilities[i].Value = v.Value);
            typedSource._totalFacilities.ForEach((v, i) => _totalFacilities[i].Value = v.Value);
            typedSource._scrappedFacilities.ForEach((v, i) => _scrappedFacilities[i].Value = v.Value);

            _activeOrbitalBatteries.Value = typedSource._activeOrbitalBatteries.Value;
            _totalOrbitalBatteries.Value = typedSource._totalOrbitalBatteries.Value;
            _scrappedOrbitalBatteries.Value = typedSource._scrappedOrbitalBatteries.Value;

            typedSource._facilityTypes.ForEach((v, i) => _facilityTypes[i] = v);

            _orbitalBatteryDesign = typedSource._orbitalBatteryDesign;

            _population.SetValues(typedSource._population);
            _morale.SetValues(typedSource._morale);
            _foodReserves.SetValues(typedSource._foodReserves);
            _creditsFromTrade.SetValues(typedSource._creditsFromTrade);

            _tradeRoutes.Clear();
            _tradeRoutes.AddRange(typedSource._tradeRoutes.Select(o => o.Clone()));

            CloneBuildings(typedSource._buildings);

            _buildSlot = typedSource._buildSlot; // TODO: clone build slot
            this.OnPropertyChanged("BuildSlot");

            _buildQueue = typedSource._buildQueue; // TODO: clone build queue
            this.OnPropertyChanged("BuildQueue");

            _baseRawMaterials = typedSource._baseRawMaterials;
        }

        private void CloneBuildings([NotNull] IKeyedCollection<GameObjectID, Building> sourceBuildings)
        {
            if (sourceBuildings == null)
                throw new ArgumentNullException("sourceBuildings");

            /*
             * Remove buildings that are not at the source.
             */
            var removedKeys = _buildings.Keys.ToHashSet();
            removedKeys.ExceptWith(sourceBuildings.Keys);
            _buildings.RemoveRange(removedKeys);

            /*
             * Add or update buildings from the source.
             */
            foreach (var sourceBuilding in sourceBuildings)
            {
                Building localBuilding;

                if (_buildings.TryGetValue(sourceBuilding.ObjectID, out localBuilding))
                    localBuilding.CloneFrom(sourceBuilding);
                else
                    _buildings.Add(sourceBuilding);
            }
        }

        protected override Cloneable CreateInstance()
        {
            return new Colony();
        }
    }

    [Serializable]
    public sealed class ColonyFacilitiesAccessor
    {
        private readonly Obtics.Values.IValueProvider<int>[] _array;

        public ColonyFacilitiesAccessor([NotNull] Obtics.Values.IValueProvider<int>[] array)
        {
            if (array == null)
                throw new ArgumentNullException("array");
            _array = array;
        }

        public Obtics.Values.IValueProvider<int> this[ProductionCategory category]
        {
            get { return _array[(int)category]; }
        }
    }

}